基础使用
import React, { useState, useCallback, useRef } from "react";
import MonacoEditor from "react-monaco-editor";
import * as monaco from "monaco-editor";
const Demo = () => {
  const [value, setValue] = useState("");
  const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>(); // 编辑器实例
  const monacoRef = useRef<typeof monaco>(); // monaco 实例
  
  // 获取编辑器实例
  const editorDidMountHandle = useCallback(
    (editor: monaco.editor.IStandaloneCodeEditor, monacoIns: typeof monaco) => {
      editorRef.current = editor;
      monacoRef.current = monacoIns;
    },[]);
  return (
    <MonacoEditor
      language="javascript"
      height="100%"
      theme="vs"
      value={value}
      onChange={setValue}
      options={{
        roundedSelection: false,
        cursorStyle: "line",
        wordWrap: "on",
      }}
      editorDidMount={editorDidMountHandle}
    />
  );
};
editorInstance 和 monacoInstance 的区别:
editorInstance:主要作用于编辑器上操作的方法,例如编辑器写入操作等。monacoInstance:主要是编辑器语言相关的内容,例如变量提示、鼠标悬浮提示等。
配置项
value: 编辑器初始显示文字language:语言支持- dimension:初始编辑器尺寸
 - inDiffEditor:是否作为 diff 编辑器中使用
 - ariaLabel:aria-label 阅读辅助标签
 - rulers:在指定的列上呈现竖线。默认为空数组
 - wordSeparators:包含单词导航时使用的单词分隔符的字符串。默认为 `~!@#$%^&*()-=+[{]}|;:'“,
 - selectionClipboard:启用Linux主剪贴板。默认值为true。
 lineNumbers:控制行号的呈现。如果它是一个函数,它将在呈现行号时被调用,并将呈现返回值。否则,如果为真值,行号将正常呈现(相当于使用一个恒等函数)。否则,将不会呈现行号。默认为'on'。- renderFinalNewline:当文件以换行符结束时,呈现最后一行号。默认值为true。
 - selectOnLineNumbers:单击行号时是否应选择相应的行。默认值为true。
 - lineNumbersMinChars:控制行号的字符宽度数量, 默认值为 5
 - glyphMargin: 是否启用字形边距,默认 false
 - lineDecorationsWidth:编辑器输入框与行号之间宽度,默认 10
 - revealHorizontalRightPadding:当显示游标时,会向游标添加一个虚拟填充(px),将其变成一个矩形。这个虚拟填充确保光标在到达视口边缘之前被显示出来。默认值为30 (px)
 - roundedSelection:呈现带有圆角边框的编辑器选择。默认值为true。
 - extraEditorClassName:添加额外的样式
 readOnly:是否只读- renderValidationDecorations:编辑器是否应该呈现验证装饰。默认为 ‘editable’。
 - scrollbar:控制滚动条的参数
 - minimap:控制缩略图
 - find:控制查找组件行为
 - fixedOverflowWidgets:将溢出小部件显示为固定状态。默认值为false。
 - overviewRulerLanes:总览标尺应该显示的垂直列数。默认为3。
 - overviewRulerBorder:控制是否应该在概览标尺周围绘制边框。默认值为true。
 - cursorBlinking:控制光标的动画样式,可能的值为'blink', 'smooth', 'phase', 'expand'和'solid'。默认为“blink”。
 - mouseWheelZoom:当使用鼠标滚轮并按住Ctrl键时,可以放大编辑器中的字体。默认值为false。
 - mouseStyle:控制鼠标指针样式,'text'或'default'或'copy'默认为'text'
 - cursorSmoothCaretAnimation:启用平滑插入符号动画。默认值为false。
 - cursorStyle:光标样式,可选 'line' | 'block' | 'underline' | 'line-thin' | 'block-outline' | 'underline-thin' 默认 'line'
 - cursorWidth:光标宽度
 - fontLigatures:是否启用 font ligatures,默认 false
 - disableLayerHinting:禁止对编辑器边距和行图层使用“transform:translate3d(0px,0px,0px)”。“transform:translate3d(0px,0px,0px)”的用法可以作为浏览器创建额外层的提示。默认值 false
 - disableMonospaceOptimizations:禁用monospace字体的优化。默认值为false。
 - hideCursorInOverviewRuler:光标是否隐藏在概览标尺中。默认值为false。
 - scrollBeyondLastLine:使滚动可以在最后一行之后移动一个屏幕大小。默认值为true。
 - scrollBeyondLastColumn:启用滚动可以超越最后一列的多个列。默认为5。
 - smoothScrolling:启用编辑器滚动到一个位置的动画。默认值为false。
 automaticLayout:编辑器将设置一个间隔时间来检查其dom节点大小是否已更改。启用此功能可能会对性能产生严重影响。默认值 falsewordWrap:设置编辑器的换行.默认 "off".- wordWrapColumn:wordWrap设置为wordWrapColumn生效,默认值 80.
 - wordWrapMinified:当文本看起来是一个缩小/生成的文件时强制换行。默认值为true。
 - wrappingIndent:控制换行行的缩进。可以是:'none', 'same', 'indent'或'deepIndent'。在vscode中默认为'same',在摩纳哥编辑器中默认为'none'
 - wrappingStrategy:换行策略. 默认 'simple'.
 - wordWrapBreakBeforeCharacters:配置换行字符。在这些字符之前将引入一个中断 Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'
 - wordWrapBreakAfterCharacters:配置换行字符。在这些字符之后将引入中断
 - stopRenderingLineAfter:停止渲染x个字符后的行 Defaults to 10000. 使用-1永不停止渲染
 - hover:配置 hover
 - links:启用检测链接并使其可单击. 默认 true.
 - colorDecorators:启用内联颜色装饰器和颜色选择器渲染
 - comments:控制编辑器中的注释行为
 - contextmenu:启用自定义快捷菜单。默认值为true。
 - mouseWheelScrollSensitivity:一个乘数,用于鼠标滚轮滚动事件的deltaX和deltaY。默认为1。
 - fastScrollSensitivity:当按下Alt键时,FastScrolling倍率默认为5。
 - multiCursorModifier:用于用鼠标添加多个游标的修饰符。默认为“alt”
 - multiCursorMergeOverlapping:合并重复选择。默认值为true
 - multiCursorPaste:配置粘贴行数等于游标数的文本时的行为。默认为“spread”。
 - suggest:提示建议
 - quickSuggestions:是否启用快速建议,默认 true
 - quickSuggestionsDelay:快速建议延迟,默认 10 毫秒
 - parameterHints:参数提示选项。
 - autoClosingBrackets:自动关闭括号的选项。默认为语言定义的行为。
 - formatOnType:在类型上启用格式。默认值为false。
 - formatOnPaste:启用粘贴格式。默认值为false。
 - dragAndDrop:控制编辑器是否允许通过拖放移动选择项。默认值为false。
 - suggestOnTriggerCharacters:启用建议框在触发字符时弹出。默认值为true。
 - acceptSuggestionOnEnter:按下回车接受建议, 默认 'on'
 - acceptSuggestionOnCommitCharacter:接受语言提供的定义字符的建议。默认值为true。
 - snippetSuggestions:使代码片段的建议。默认为true。
 - emptySelectionClipboard:不带选定项的复制复制当前行。
 - copyWithSyntaxHighlighting:复制语法高亮显示。
 - suggestSelection:历史模式建议。
 - suggestFontSize:建议字体大小
 - suggestLineHeight:建议文字行高
 - tabCompletion:启用选项卡
 - selectionHighlight:使选择突出显示。默认值为true。
 - occurrencesHighlight:启用语义突出显示。默认值为true。
 - codeLens:显示code len默认为 true。
 - lightbulb:控制代码动作灯泡的行为和呈现。
 - codeActionsOnSaveTimeout:保存时运行代码操作的 time。
 - folding:支持代码折叠。默认值为true。
 - foldingStrategy:选择折叠策略。'auto'使用为当前文档提供的策略,'indentation'使用基于缩进的折叠策略。默认为“auto”。
 - foldingHighlight:对折叠区域启用高亮显示。默认值为true。
 - showFoldingControls:控制边栏中的折叠操作是始终可见还是隐藏,除非鼠标在边栏上方。默认为“mouseover”。
 - matchBrackets:启用匹配方括号的高亮显示。默认为“always”。
 - renderWhitespace:启用空白的呈现。默认为 none。
 - renderControlCharacters:启用控制字符的呈现。默认值为false。
 - renderIndentGuides:启用缩进线的呈现。默认值为true。
 - highlightActiveIndentGuide:启用突出显示活动缩进指南。默认值为true。
 - renderLineHighlight:启用当前行高亮显示。默认为 all。
 - useTabStops:是否在制表位之后插入和删除空格。
 - fontFamily:字体
 - fontWeight:字体宽度
 - fontSize:字体大小
 - lineHeight: 行高
 - letterSpacing:字间距
 
插入内容
方式一:模拟键盘输入从光标后插入
const str = '待插入的内容'
editorInstance?.trigger('keyboard', 'type', { text: str });
方式二:直接从光标处插入内容
// 快速插入
const code = '待插入的内容'
const position = editorInstance?.getPosition();
const selection = editorInstance?.getSelection();
editorInstance?.executeEdits('', [
  {
    range: {
      startLineNumber: position?.lineNumber || 0,
      startColumn: position?.column || 0,
      endLineNumber: selection?.endLineNumber || position?.lineNumber || 0,
      endColumn: selection?.endColumn || position?.column || 0,
    } as any,
    text: code,
  },
]);
代码格式化
// 格式化
await editorInstance?.getAction('editor.action.formatDocument').run();
变量提示
import React, { useState, useCallback, useRef, useEffect } from "react";
import MonacoEditor from "react-monaco-editor";
import * as monaco from "monaco-editor";
const Demo = () => {
  const [value, setValue] = useState("");
  const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>(); // 编辑器实例
  const monacoRef = useRef<typeof monaco>(); // monaco 实例
  const monacoHoverProviderRef = useRef<monaco.IDisposable>(); // monaco 注册缓存
  const monacoCompletionItemProviderRef = useRef<monaco.IDisposable>(); // monaco 注册缓存
  useEffect(() => {
    // 为编辑器注入变量提示
    monacoCompletionItemProviderRef.current =
      monacoRef.current?.languages.registerCompletionItemProvider(
        "javascript",
        {
          provideCompletionItems: (model, position) => {
            // 只对当前编辑器实例进行处理
            if (model.uri.toString() === editorRef.current?.getModel()?.uri.toString()) {
              // 自定义变量提示规则
              return {
                suggestions,
              };
            }
          },
          triggerCharacters: ["."],
        }
      );
    // 鼠标悬浮提示
    monacoHoverProviderRef.current = monaco.languages.registerHoverProvider(
      "javascript",
      {
        provideHover: (model, position) => {
          // 只对当前编辑器实例进行处理
          if (model.uri.toString() !== editorRef.current?.getModel()?.uri.toString()) {
            // 自定义变量提示规则
            return {
              contents,
            };
          }
        },
      }
    );
    return () => {
      // 退出时,销毁注册的内容避免重复创建
      monacoCompletionItemProviderRef.current?.dispose();
      monacoHoverProviderRef.current?.dispose();
    };
  }, []);
  // 获取编辑器实例
  const editorDidMountHandle = useCallback(
    (editor: monaco.editor.IStandaloneCodeEditor, monacoIns: typeof monaco) => {
      editorRef.current = editor;
      monacoRef.current = monacoIns;
    },
    []
  );
  return (
    <MonacoEditor
      language="javascript"
      height="100%"
      theme="vs"
      value={value}
      onChange={setValue}
      options={{
        roundedSelection: false,
        cursorStyle: "line",
        automaticLayout: true,
        selectOnLineNumbers: true,
        wordWrap: "on",
      }}
      editorDidMount={editorDidMountHandle}
    />
  );
};
为了避免变量被重复注册,因此需要 ref 存一下变量提示实例,在销毁阶段注销变量注册。  
为了便于动态变量规则的使用,提供了一个的类,用于计算变量层级提示。
import { isArray } from 'lodash';
import * as monaco from 'monaco-editor';
import {
  FormatFunctionProps,
  InsertFunctionProps,
} from '@/routes/ScriptEvent/EditPage/types/function';
interface MonacoVariableTipsProps {
  variableData: FormatFunctionProps[];
  level: number;
}
interface TipProps {
  word: string;
  remark?: string;
}
export class MonacoVariableTips {
  // 当前行输入的连续内容
  content: string | undefined;
  // 当前行输入的内容
  lineContent: string | undefined;
  // 刚输入的内容
  input: string | undefined;
  // 当前行输入的单词
  word: string | undefined;
  private model: monaco.editor.ITextModel | undefined;
  private position: monaco.Position | undefined;
  // 要提示的层级, 默认提示下一层
  private readonly level: number;
  // 完整的变量数据
  private readonly variableData: FormatFunctionProps[];
  /**
   * 为 monaco 编辑器添加变量提示
   * @param props
   */
  constructor(props: MonacoVariableTipsProps) {
    this.level = props.level || 1;
    this.variableData = props.variableData;
  }
  /**
   * 是否由 . 触发
   */
  get isDotTrigger() {
    return this.input === '.';
  }
  /**
   * 解析当前行
   */
  parseContinuousContent(model: monaco.editor.ITextModel, position: monaco.Position) {
    if (!position || !model) {
      throw new Error('position | model is undefined');
    }
    this.model = model;
    this.position = position;
    const { column, lineNumber } = this.position;
    this.lineContent = this.model.getLineContent(lineNumber);
    this.content =
      this.lineContent
        .slice(0, column - 1)
        .split(' ')
        .pop() || '';
    this.input = this.lineContent[column - 2];
    this.word = this.model.getWordAtPosition(position)?.word;
  }
  /**
   * 解析内容为变量层级
   * @param str
   * @param isFlat 是否打平为一维字符串数组
   */
  static analysisWords(str: string = '', isFlat?: boolean) {
    // 把 ... 替换成 ***
    const _str = str.replace(/\.\.\./g, '***');
    const res = _str
      .split('.')
      .filter(Boolean)
      .map((v) => {
        return {
          word: v.replace(/\*\*\*/g, '...'),
          match: v.match(/^\w*/g)?.join('')!,
        };
      });
    if (isFlat) {
      return res.map((v) => v.match);
    }
    return res;
  }
  /**
   * 根据输入在变量匹配 tree 中搜索命中的层级
   * @param inputWords
   */
  searchFunctionsVariable(inputWords: string[]) {
    let res: FormatFunctionProps[] = [];
    let target: FormatFunctionProps | undefined;
    inputWords.forEach((word, index) => {
      if (!target) {
        target = this.variableData.find((v) => v.match.startsWith(word));
        if (target && inputWords.length === index + 1) {
          // 最后一层
          res = [target];
        }
      } else if (inputWords.length === index + 1) {
        // 表示已经到目标最后一层,进行模糊搜索
        const r = target.children.filter((v) => v.match.startsWith(word));
        res = r;
      } else {
        target = target.children.find((v) => v.match.startsWith(word));
      }
    });
    return res;
  }
  /**
   * 深搜结果,拉平成一维数组
   * @param searchResult
   * @param isDotTrigger
   * @param inputLength
   */
  dfsSearchResult(searchResult: FormatFunctionProps[], isDotTrigger: boolean, inputLength: number) {
    const tips: TipProps[] = [];
    const { level } = this;
    const clearStartDot = (word: string = '') => {
      if (word.startsWith('.')) {
        return word.substring(1);
      }
      return word;
    };
    dfs(searchResult, { word: '' }, 0, {});
    function dfs(curSearchResult, curTip, curLevel, parentSearchResult) {
      // 边界
      if (!curSearchResult || curSearchResult.length === 0 || curLevel > level) {
        if (isDotTrigger) {
          if (curLevel === level) {
            // eslint-disable-next-line no-param-reassign
            curTip.word = curTip.word.substring(parentSearchResult.parentWord.length + 1);
          } else if (curLevel > level) {
            // eslint-disable-next-line no-param-reassign
            curTip.word = curTip.word.substring(inputLength + 1);
          }
        }
        // eslint-disable-next-line no-param-reassign
        curTip.word = clearStartDot(curTip.word);
        if (curTip.word) {
          tips.push(curTip);
        }
        return;
      }
      // 添加父级数据
      if (curLevel > 0) {
        const _tip = { ...curTip };
        if (!isDotTrigger) {
          _tip.word = clearStartDot(_tip.word);
          if (_tip.word) {
            tips.push(_tip);
          }
        }
      }
      // 如果还没到层级继续往下走
      curSearchResult.forEach((item) => {
        dfs(
          item.children,
          {
            ...curTip,
            word: `${curTip.word}.${item.word}`,
            remark: item.remark || curTip.remark,
          },
          curLevel + 1,
          item
        );
      });
    }
    return tips;
  }
  /**
   * 生成提示
   * @param tips
   */
  generateSuggestions(tips: TipProps[]) {
    return tips.map((item) => ({
      label: item.word,
      kind: monaco.languages.CompletionItemKind.Function,
      insertText: item.word,
      documentation: item.remark,
      detail: item.remark,
      // range: {
      //   startLineNumber: 1,
      // },
    }));
  }
  /**
   * 生成悬浮提示数据格式
   * @param hoverTips
   */
  generateHover(hoverTips: FormatFunctionProps[]) {
    return hoverTips.map((item) => ({
      value: item.remark || '',
      isTrusted: true,
      supportThemeIcons: true,
    }));
  }
  getSuggestions() {
    const words = MonacoVariableTips.analysisWords(this.content, true) as string[];
    const searchResult = this.searchFunctionsVariable(words);
    const inputLength = words.join('').length;
    const tips = this.dfsSearchResult(searchResult, this.isDotTrigger, inputLength);
    const suggestions = this.generateSuggestions(tips);
    return suggestions;
  }
  getHover() {
    const words = MonacoVariableTips.analysisWords(this.content, true) as string[];
    if (isArray(words) && words.length > 0) {
      words[words.length - 1] = this.word!;
    }
    const hoverTips = this.searchFunctionsVariable(words);
    const hovers = this.generateHover(hoverTips);
    return hovers;
  }
}
相关类型文件
export interface FormatFunctionProps {
  word: string;
  match: string;
  children: FormatFunctionProps[];
  remark?: string;
  parentWord?: string;
}
仅需按照 FormatFunctionProps[] 类型传入变量规则即可,具体见项目文件。
useEffect(() => {
  if (!isEmpty(variableData)) {
    const monacoVariableTips = new MonacoVariableTips({ variableData, level: 2 });
    // 为编辑器注入变量提示
    monacoCompletionItemProviderRef.current = monacoRef.current?.languages.registerCompletionItemProvider(
      'javascript',
      {
        provideCompletionItems: (model, position) => {
          // 只对当前编辑器实例进行处理
          if (model.uri.toString() !== editorRef.current?.getModel()?.uri.toString()) {
            monacoVariableTips.parseContinuousContent(model, position);
            return {
              suggestions: monacoVariableTips.getSuggestions() as any,
            };
          }
        },
        triggerCharacters: ['.'],
      }
    );
    // 鼠标悬浮提示
    monacoHoverProviderRef.current = monaco.languages.registerHoverProvider('javascript', {
      provideHover: (model, position) => {
        // 只对当前编辑器实例进行处理
        if (model.uri.toString() !== editorRef.current?.getModel()?.uri.toString()) {
          monacoVariableTips.parseContinuousContent(model, position);
          return {
            contents: monacoVariableTips.getHover(),
          };
        }
      },
    });
  }
  return () => {
    // 退出时,销毁注册的内容避免重复创建
    monacoCompletionItemProviderRef.current?.dispose();
    monacoHoverProviderRef.current?.dispose();
  };
}, [variableData]);
撤销/重做
// 撤销
editorInstance.trigger('keyboard', 'undo', null);
// 重做
editorInstance.trigger('keyboard', 'redo', null);
汉化
安装 webpack 插件 monaco-editor-chinese-plugin,并在 config/config.ts 中配置
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const MonacoChinesePlugin = require('monaco-editor-chinese-plugin');
const config: IConfig = {
  chainWebpack: (c) => {
    c.plugin('monaco-editor-webpack-plugin').use(new MonacoWebpackPlugin({
      languages: ['javascript', 'typescript', 'sql', 'json'],
    }));
    c.plugin('monaco-editor-chinese-plugin').use(new MonacoChinesePlugin());
    // ......
  },
  // 其余配置 ......
};
export default config;
断点调试效果

实现一个能展示断点调试效果的编辑器。
代码如下:
index.jsx
import React, { useCallback, useEffect, useRef } from 'react';
import MonacoEditor from 'react-monaco-editor';
import { MonacoEditorProps } from 'react-monaco-editor/src/types';
import * as monaco from 'monaco-editor';
import { isNumber, uniqWith } from 'lodash';
import './editor.css';
interface Props extends MonacoEditorProps {
  breakPoints: monaco.editor.IModelDeltaDecoration[]; // 断点标记
  onBreakPointsChange?: (breakPoints: monaco.editor.IModelDeltaDecoration[]) => void;
  onBreakPointsDataChange?: () => void; // 断点数据发生了变化
  stopBreakpoint?: number; // 当前断点停在哪一行
}
/**
 * 基于 monaco-editor 的设计的调试编辑器
 * @constructor
 */
const MonacoDebugger: React.FC<Props> = ({
  editorDidMount,
  onBreakPointsChange,
  onBreakPointsDataChange,
  breakPoints,
  stopBreakpoint,
  ...props
}) => {
  const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>(); // 编辑器实例
  const monacoRef = useRef<typeof monaco>(); // monaco 实例
  const decorationsRef = useRef<string[] | null>([]); // 装饰器缓存
  const stopBreakpointRef = useRef<number | undefined>(stopBreakpoint); // 当前断点停在哪一行
  useEffect(() => {
    stopBreakpointRef.current = stopBreakpoint;
    setCurrentBreakPoint(stopBreakpoint);
  }, [stopBreakpoint]);
  useEffect(() => {
    const model = editorRef.current?.getModel();
    if (model) {
      model.deltaDecorations([], breakPoints);
    }
  }, [breakPoints]);
  useEffect(() => {
    // 编辑器鼠标移动事件
    editorRef.current?.onMouseMove((e) => {
      if (
        e.target.detail &&
        e.target.detail.offsetX &&
        e.target.detail.offsetX >= 0 &&
        e.target.detail.offsetX <= 50
      ) {
        const line = e.target.position?.lineNumber;
        addFakeBreakPoint(line);
      } else {
        removeFakeBreakPoint();
      }
    });
    // 编辑器鼠标离开事件
    editorRef.current?.onMouseLeave(() => {
      removeFakeBreakPoint();
    });
    // 编辑器鼠标点击事件
    editorRef.current?.onMouseDown((e) => {
      if (
        e.target.detail &&
        e.target.detail.offsetX &&
        e.target.detail.offsetX >= 0 &&
        e.target.detail.offsetX <= 50
      ) {
        const line = e.target.position?.lineNumber || 0;
        let isChangeFlag = false;
        if (!hasBreakPoint(line)) {
          if (editorRef.current?.getModel()?.getLineContent(line).trim() !== '') {
            addBreakPoint(line);
            isChangeFlag = true;
          }
        } else {
          removeBreakPoint(line);
          isChangeFlag = true;
        }
        if (isChangeFlag) {
          onBreakPointsDataChange?.();
        }
      }
    });
  }, []);
  // 设置当前断点停在哪一行
  const setCurrentBreakPoint = (line?: number) => {
    const allDecorations = editorRef.current?.getModel()?.getAllDecorations();
    // 取消行上的装饰器
    const cancelLinesClassName = () => {
      if (!allDecorations) return;
      const lineBgDecorations = allDecorations.filter((v) => v.options.className);
      const lineBgIds = lineBgDecorations.map((v) => v.id);
      editorRef.current?.deltaDecorations(lineBgIds, []);
    };
    if (isNumber(line)) {
      cancelLinesClassName();
      // 添加断点行装饰器
      const curLineDecoration = {
        range: new monacoRef.current!.Range(line, 1, line, 1),
        options: {
          isWholeLine: true,
          className: 'breakpoints-line',
        },
      };
      editorRef.current?.deltaDecorations([], [curLineDecoration]);
    } else {
      cancelLinesClassName();
    }
  };
  const handleDeltaDecorationsUpdate = useCallback(() => {
    const model = editorRef.current?.getModel();
    if (model) {
      let _breakPoints = model
        .getAllDecorations()
        .filter((decoration) => decoration.options.linesDecorationsClassName === 'breakpoints')
        .map((v) => ({ range: v.range, options: v.options }));
      _breakPoints = uniqWith(_breakPoints, (arrVal, othVal) => {
        return arrVal.range.startLineNumber === othVal.range.startLineNumber;
      });
      onBreakPointsChange?.(_breakPoints);
    }
  }, []);
  // 该位置上是否已经存在断点
  const hasBreakPoint = useCallback((line) => {
    const decorations = editorRef.current?.getLineDecorations(line);
    if (decorations?.length) {
      for (const decoration of decorations) {
        if (decoration.options.linesDecorationsClassName === 'breakpoints') {
          return true;
        }
      }
    }
    return false;
  }, []);
  // 添加提示断点
  const addFakeBreakPoint = useCallback((line) => {
    const fakeBreakPointValue = {
      range: new monacoRef.current!.Range(line, 1, line, 1),
      options: {
        isWholeLine: true,
        linesDecorationsClassName: 'breakpoints-fake',
      },
    };
    if (decorationsRef.current) {
      decorationsRef.current =
        editorRef.current?.deltaDecorations(decorationsRef.current, [fakeBreakPointValue]) || [];
    }
  }, []);
  // 删除提示断点
  const removeFakeBreakPoint = useCallback(() => {
    if (decorationsRef.current) {
      decorationsRef.current =
        editorRef.current?.deltaDecorations(decorationsRef.current, []) || [];
    }
  }, []);
  // 添加断点
  const addBreakPoint = useCallback((line) => {
    const model = editorRef.current?.getModel();
    if (!model) return;
    if (monacoRef.current) {
      const breakPointValue = {
        range: new monacoRef.current.Range(line, 1, line, 1),
        options: {
          isWholeLine: true,
          linesDecorationsClassName: 'breakpoints',
        },
      };
      model.deltaDecorations([], [breakPointValue]);
      handleDeltaDecorationsUpdate();
    }
  }, []);
  // 删除断点
  const removeBreakPoint = useCallback((line) => {
    if (editorRef.current) {
      const model = editorRef.current?.getModel();
      if (!model) return;
      let decorations;
      const ids: string[] = [];
      if (line !== undefined) {
        decorations = editorRef.current.getLineDecorations(line);
      } else {
        decorations = editorRef.current.getModel()?.getAllDecorations();
      }
      for (const decoration of decorations) {
        if (decoration.options.linesDecorationsClassName === 'breakpoints') {
          ids.push(decoration.id);
        }
      }
      if (ids?.length) {
        model.deltaDecorations(ids, []);
      }
      handleDeltaDecorationsUpdate();
    }
  }, []);
  // 获取编辑器实例
  const editorDidMountHandle = useCallback(
    (editor: monaco.editor.IStandaloneCodeEditor, monacoIns: typeof monaco) => {
      editorRef.current = editor;
      monacoRef.current = monacoIns;
      editorDidMount?.(editor, monacoIns);
    },
    []
  );
  return <MonacoEditor editorDidMount={editorDidMountHandle} {...props} />;
};
export default MonacoDebugger;
editor.css 文件
.breakpoints-fake {
  width: 18px !important;
  height: 18px !important;
  left: 0 !important;
  top: 0 !important;
  cursor: pointer !important;
}
.breakpoints-fake::after {
  content: '';
  position: absolute;
  width: 10px;
  height: 10px;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: #F23A50;
  opacity: 0.5;
  cursor: pointer;
}
.breakpoints {
  width: 10px !important;
  height: 10px !important;
  left: 9px !important;
  top: 50% !important;
  transform: translate(-50%, -50%) !important;
  border-radius: 50%;
  background: #F23A50;
  cursor: pointer;
}
.breakpoints-line {
  background-color: rgba(242,58,80,0.4);
}
添加鼠标右键菜单
主要是使用 monaco 实例上的 addAction 方法,添加一个右键菜单。为了方便使用可以封装成一个 hook。
这里以添加一个 js 代码美化功能的右键菜单为例,代码如下:
import * as monaco from 'monaco-editor';
import beautify from 'js-beautify';
import { useEffect } from 'react';
/**
 * 为 monaco-editor 右键选项添加代码美化功能
 * @param editorInstance
 */
const useEditorBeautifyJsCode = (editorInstance?: monaco.editor.IStandaloneCodeEditor) => {
  useEffect(() => {
    if (editorInstance) {
      editorInstance.addAction({
        id: 'beautifyJsCode',
        label: '美化 JS 代码',
        keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyF],
        contextMenuGroupId: '2_customCommand',
        run() {
          const oldCode = editorInstance.getValue();
          const newCode = beautify(oldCode, {
            indent_size: 2,
          });
          editorInstance.executeEdits('', [
            {
              range: new monaco.Range(1, 1, editorInstance.getModel()!.getLineCount() + 1, 1),
              text: newCode,
            },
          ]);
        },
      });
    }
  }, [editorInstance]);
};
export default useEditorBeautifyJsCode;
使用
import React, { useState, useCallback, useRef } from "react";
import MonacoEditor from "react-monaco-editor";
import { EditorDidMount} from 'react-monaco-editor/lib/types';
import useEditorBeautifyJsCode from './useEditorBeautifyJsCode'
const Demo = () => {
  const editorRef = useRef<Parameters<EditorDidMount>[0]>(); // 编辑器实例
  
  useEditorBeautifyJsCode(editorRef.current);
  // 获取编辑器实例
  const editorDidMountHandle: EditorDidMount = useCallback(
    (editor) => {
      editorRef.current = editor;
    },[]);
  return (
    <MonacoEditor
      language="javascript"
      height="100%"
      theme="vs"
      options={{
        roundedSelection: false,
        cursorStyle: "line",
        wordWrap: "on",
      }}
      editorDidMount={editorDidMountHandle}
    />
  );
};
